package com.hx.listener;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator;
import org.springframework.dao.DataAccessException;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * 增强型仓库数据导入监听器（用于EasyExcel处理百万级数据导入）
 * 功能：逐行读取Excel数据 → 校验 → 批量入库 → 错误处理 → 生成错误报告
 */
@Slf4j
public class ImportDataSyncListener<T> extends AnalysisEventListener<T> {
    private final Class<T> tClass;
    // 动态批次大小（默认初始值50000条，根据处理情况自动调整）
    private int batchSize = 50;

    private final Validator validator;

    // 数据缓存列表（存储当前批次待处理的合法数据）
    private List<T> cachedList = new ArrayList<>(batchSize);

    // 仓库服务接口（用于调用数据库批量插入操作）
    private Consumer<T> consumer;
    private Consumer<String> notice;
    private List<String> validateColumns;

    // 原子计数器（保证多线程安全）：
    private final AtomicLong totalCounter = new AtomicLong(0);   // 总处理数据量统计
    private final AtomicLong successCounter = new AtomicLong(0); // 成功入库数据量统计
    private final AtomicLong errorCounter = new AtomicLong(0); // 成功入库数据量统计

    // 错误记录列表（线程安全的同步列表，存储所有校验失败或插入失败的数据）
    private final List<T> errorRecords = Collections.synchronizedList(new ArrayList<>());

    public ImportDataSyncListener(Consumer<T> consumer, Consumer<String> notice, List<String> validateColumns, Class<T> tClass) {
        this.consumer = consumer;
        this.notice = notice;
        this.tClass = tClass;
        this.validateColumns = validateColumns;
        this.validator = Validation.byProvider(HibernateValidator.class).configure()
                .failFast(false)
                .buildValidatorFactory()
                .getValidator();
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        errorCounter.incrementAndGet();
        ReadRowHolder readRowHolder = context.readRowHolder();
        Map<Integer, ReadCellData<?>> cellDataMap = (Map) readRowHolder.getCellMap();
        readRowHolder.setCurrentRowAnalysisResult(cellDataMap);
        /*int rowIndex = readRowHolder.getRowIndex();
        int currentHeadRowNumber = context.readSheetHolder().getHeadRowNumber();
        //是否表头
        boolean isHead = rowIndex < currentHeadRowNumber;
        if (isHead) {
            throw new BusinessException("文件表头字段与模板不一致");
        }*/
        String fieldType = null;
        String errorInfo = "数据格式有误";

        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            int rowIndex = excelDataConvertException.getRowIndex() + 1;
            int columIndex = excelDataConvertException.getColumnIndex() + 1;
            //log.error("第{}行，第{}列解析异常", rowIndex, columIndex);
            Class<?> type = excelDataConvertException.getExcelContentProperty().getField().getType();
            if (type == LocalDate.class) {
                fieldType = "日期";
            } else if (type == LocalDateTime.class) {
                fieldType = "时间";
            } else if (type == Integer.class) {
                fieldType = "整数";
            } else if (type == BigDecimal.class) {
                fieldType = "数字";
            }
            errorInfo = StrUtil.format("第[{}行{}列]数据格式有误，原因：数据格式须【{}】类型", rowIndex, convertColumnNumberToExcelColumn(columIndex), fieldType);
        }
        //T errorData = tClass.getDeclaredConstructor().newInstance();
        //errorData.setErrorMsg(columIndex > 0 ? StrUtil.format("第{}列数据格式有误,不是{}类型", columIndex, fieldType) : "数据格式有误");
        log.error("{}", errorInfo);
        notice.accept(errorInfo);
        //errorRecords.add(errorData);
    }

    /**
     * 核心处理方法：每读取一行Excel数据时触发
     *
     * @param data    当前行的数据对象
     * @param context EasyExcel解析上下文（包含工作表、行号等信息）
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        if (validate(data, context)) {
            successCounter.incrementAndGet();
            //log.info("{}", data);
            try {
                /*
                Method m = data.getClass().getMethod("setRowNum",Integer.class);
                m.invoke(data, context.readRowHolder().getRowIndex()+1);
                */
                Field subjectField = tClass.getDeclaredField("rowNum");
                subjectField.setAccessible(true);
                subjectField.set(data, context.readRowHolder().getRowIndex() + 1);
                consumer.accept(data);
                //printProgress();
            } catch (Exception e) {
            }
            /*cachedList.add(data);
            //当缓存数据量达到批次大小时触发批量插入
            if (cachedList.size() >= batchSize) {
                List<T> sub = CollUtil.sub(cachedList, 0, batchSize);
                consumer.accept(sub); // 动态调整批次大小
                cachedList.clear();
            }*/
        }
        //定期打印处理进度（每处理1万条打印一次）
        //printProgress();
    }

    private boolean validate(T data, AnalysisContext context) {
        /*Set<ConstraintViolation<T>> violationSet = validator.validate(data, Default.class);
        violationSet.stream().forEach(it -> {
            log.info("{}", it);
            notice.accept(StrUtil.format("第[{}行]数据不满足要求，原因：{}", context.readRowHolder().getRowIndex() + 1, it.getMessage()));
        });
        return violationSet.isEmpty();*/
        Set<ConstraintViolation<T>> validateSet = null;
        if (CollectionUtil.isEmpty(validateColumns)) {
            validateSet = validator.validate(data, Default.class);
        } else {
            validateSet = validateColumns.stream().map(field -> validator.validateProperty(data, field, Default.class)).flatMap(it->it.stream()).collect(Collectors.toSet());
        }
        if (CollectionUtil.isEmpty(validateSet)) {
            return true;
        }
        errorCounter.incrementAndGet();
        String msg  = validateSet.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("；"));
        //log.error("校验：{}", msg);
        notice.accept(StrUtil.format("第[{}行]数据校验有误，原因：{}", context.readRowHolder().getRowIndex() + 1, msg));
        return false;
    }

    /*private boolean validateData(T data) {
        // 示例校验逻辑
        //return ObjectUtil.isNotNull(data.getCode());
        //&& data.getAreaZD() != null
        //&& data.getCangKuDate() != null;
        return true;
    }*/

    /**
     * 批量保存数据到数据库（核心插入逻辑）
     */
    private void saveBatch() {
        if (!cachedList.isEmpty()) {
            try {
                //cangKuService.batchInsert(cachedList);
                //cangKuService.saveBatch(cachedList);
                // 成功计数累加
                successCounter.addAndGet(cachedList.size());
            } catch (DataAccessException e) {
                log.error("批次插入失败，失败数量：{}", cachedList.size(), e);
                errorRecords.addAll(cachedList);
            }
            //清空缓存列表（无论成功与否）
            cachedList.clear();
        }
    }

    /**
     * 动态调整批次大小（示例算法：根据处理情况自动优化）
     * 说明：如果批次过小会增加数据库交互次数，过大会导致内存压力
     */
    private void adjustBatchSize() {
        // 动态调整逻辑（示例）
        if (batchSize < 50000) {
            batchSize = Math.min(batchSize * 2, 50000);
        }
    }

    private void printProgress() {
        long total = totalCounter.incrementAndGet();
        if (total % 200 == 0) {
            String format = StrUtil.format("已处理：{} 条", total);
            log.info(format);
            notice.accept(format);
        }
    }

    /**
     * 所有数据解析完成后触发的收尾操作
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //generateErrorReport(); // 生成错误报告
        String format = StrUtil.format("导入完成！Excel总计：{} 条，其中数据不合格：{}条，合格：{}条", successCounter.get() + errorCounter.get(), errorCounter.get(), successCounter.get());
        log.info(format);
        notice.accept(format); // 处理最后一批数据
    }

    /*private void generateErrorReport() {
        if (!errorRecords.isEmpty()) {
            String errorFilePath = "/tmp/import_errors_" + System.currentTimeMillis() + ".xlsx";
            EasyExcel.write(errorFilePath, ExcelFieldData.class)
                    .sheet("sheet1")
                    .doWrite(errorRecords);
            log.warn("错误数据已导出至：{}", errorFilePath);
        }
    }*/
    private static String convertColumnNumberToExcelColumn(int columnNumber) {
        StringBuilder columnName = new StringBuilder();
        while (columnNumber > 0) {
            int remainder = (columnNumber - 1) % 26; // 因为Excel的A列为1，所以减去1后对26取余
            columnName.insert(0, (char)('A' + remainder)); // 将数字转换为对应的字母并插入到字符串的开始位置
            columnNumber = (columnNumber - remainder) / 26; // 更新列号
        }
        return columnName.toString();
    }
}